package com.gr.experimental.model.impl.distr;

import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import com.gr.experimental.model.impl.distr.operations.CountRVClassFunction;
import com.gr.experimental.model.impl.distr.operations.CountRVStateFunction;
import com.gr.experimental.model.impl.distr.operations.Function;
import com.gr.experimental.model.impl.distr.operations.MethodFunctionAdapter;

public class FunctionRegistry {

	private FunctionRegistry() {
		
	}
	
	private static Map<String, Set<Function>> functionMap = new  HashMap<String, Set<Function>>();
	
	private static void addFunctionMethodAdapter(Class<?> aClass, String methodName)  {
		Method[] methods = aClass.getMethods();
		for (Method method : methods) {
			if (method.getName().equals(methodName)) {
				addToMultiMap(functionMap, methodName, new MethodFunctionAdapter(method));
			}
		}
	}
	
	private static void addFunction(Function function)  {
		addToMultiMap(functionMap, function.getName(), function);
	}

	private static <U,T> void addToMultiMap(Map<U,Set<T>> map, U key, T value) {
		Set<T> set = map.get(key);
		if (set == null) {
			set = new HashSet<T>();
			map.put(key, set);
		}
		set.add(value);
	}
	
	static {
		addFunctionMethodAdapter(BuiltInFunctions.class,"max");
		addFunction(new CountRVStateFunction());
		addFunction(new CountRVClassFunction());
	}
	
	public static Set<Function> getFunctionsForName(String name) {
		return functionMap.get(name);
	}
	
	public static Function getFunction(String name, Class<?>[] parameterTypes) {
		
		for (Function fun: getFunctionsForName(name)) {
			Class<?>[] funParameterTypes = fun.getParameterTypes();

			if (parameterTypeNamesAssignable(funParameterTypes,parameterTypes)) {
				return fun;
			}
		}
		return null;
	}
	
	/**
	 * Returns the function that fits best to the given list of arguments.
	 * 
	 * Currently calls getFunction internally. It has to be reimplemented.
	 * @param name the name of the function
	 * @param parameterTypes parameter types of the function
	 * @return the best fitting function
	 */
	public static Function findClosestFunction(String name, Class<?>[] parameterTypes) {
		return getFunction(name, parameterTypes);
	}
	
	public static Set<String> getFunctionNames() {
		return functionMap.keySet();
	}
	
	public static Map<String, Set<Function>> getFunctionMap() {
		return Collections.unmodifiableMap(functionMap);
	}
	
	private static boolean parameterTypeNamesAssignable(Class<?>[] targetParameterTypes, Class<?>[] queryParameterTypes) {
		
		if (targetParameterTypes.length != queryParameterTypes.length) return false;
		
		for (int i=0; i < targetParameterTypes.length; i++) {
			if (! targetParameterTypes[i].isAssignableFrom((queryParameterTypes[i]))) {
				return false;
			}
		}
		return true;
	}
	
}
